Part 2
ABAQUS INP COMPREHENSIVE ANALYZER
Under the Hood — A Deep-Dive Series
PART 2
The Geometry Engine
Exterior Surfaces, Tessellation, the 3D Viewer, and STL Export
Joseph P. McFadden Sr.
McFaddenCAE.com | The Holistic Analyst
© 2026 Joseph P. McFadden Sr. All rights reserved.
Recap and Setup — Where Part One Left Us
In Part One of this series, we followed the model processing pipeline from file selection through the complete analysis sequence. We watched the file reader recurse through include directives, the keyword state machine count and categorize every entity in the model, the material-section-part mapping assemble the material DNA, the node coordinates land in memory with part-scoped keys to prevent ID collision, and the three-tier part identification logic reverse-engineer the assembly structure.
At the end of that sequence, the program had a complete structured picture of the model: parts identified, nodes located, elements connected, volumes calculated, relationships mapped.
But none of that is visible yet. It is all data in memory. Numbers in dictionaries.
Part Two is about turning that data into geometry — extracting the surfaces that define what each part looks like from the outside, rendering those surfaces in a three-dimensional viewer, and exporting them as STL files that other tools can consume.
This is where the mathematics of the mesh meets the requirements of visualization, and where a set of decisions about efficiency, quality, and correctness have to be made explicitly rather than assumed.
Section 1 — The Fundamental Problem: What Is the Outside of a Mesh?
A finite element mesh is a volume filling. It is not a surface. The elements pack together to fill the interior of a solid body. The nodes define corners. The elements define how those corners connect to form cells. The cells stack together until the entire volume is covered.
When you look at a physical part — a housing, a bracket, a screw — what you see is the exterior surface. You do not see the interior material. You see the boundary between the part and the air around it.
In a finite element mesh, that boundary is made up of element faces. Every solid element has faces — flat polygonal surfaces connecting subsets of its nodes. An eight-noded brick element has six rectangular faces. A four-noded tetrahedron has four triangular faces.
Here is the key insight that drives the entire geometry engine.
An interior face is shared between exactly two elements. Element A and element B sit next to each other. The face between them belongs to both. When you look at the part from outside, you cannot see that face — it is buried inside.
An exterior face belongs to exactly one element. It sits on the boundary of the part where no neighboring element exists on the other side. That face is what you see when you look at the part.
So the algorithm to extract the visible exterior surface of any mesh, regardless of element type or mesh complexity, is this: count how many times each unique face appears across all elements. Faces that appear exactly once are exterior. Faces that appear exactly twice are interior. Discard the interior faces. Everything that remains is the surface.
This is sometimes called the boundary face algorithm or the unique face algorithm, and it is one of the most elegant and general solutions in computational geometry. It makes no assumptions about the shape of the mesh, the element types used, or whether the mesh has any holes or concavities. It works on any manifold solid mesh without modification.
Consider what this means. You are not tracking neighbors. You are not doing spatial comparisons. You are not raycasting. You are simply counting. The information about what is inside and what is outside is already embedded in the connectivity table — it just has to be surfaced by counting how many times each face appears. That is a profound example of extracting hidden information through a counting operation rather than through geometric computation.
Section 2 — Face Topology: Every Element Type's Anatomy
To implement the face counting algorithm, you need a face topology table — a lookup that tells you, for each element type, which subsets of its node list form the faces.
This is one of those places where domain knowledge is non-negotiable. The code cannot figure out the face topology from first principles. Someone has to know the geometry of each element and encode it.
The Hexahedral Family — The Eight-Noded Brick
The C3D8 family — C3D8, C3D8R, C3D8I, C3D8H — is the eight-noded brick. Think of a slightly distorted cube. The eight nodes sit at the eight corners. The element has exactly six faces, each defined by four of those eight nodes.
Face 1 is the bottom: nodes at positions 1, 2, 3, 4 in the connectivity list. Face 2 is the top: nodes 5, 6, 7, 8. Face 3 is the front: nodes 1, 2, 6, 5. Face 4 is the back: nodes 4, 3, 7, 8. Face 5 is the right: nodes 2, 3, 7, 6. Face 6 is the left: nodes 1, 5, 8, 4.
The exact ordering within each face is the winding order — which we will come back to. For counting purposes, order does not matter. What matters is which set of node IDs defines each face.
The Tetrahedral Family — The Four-Noded Tet
The C3D4 is the four-noded linear tetrahedron. Four nodes, four triangular faces. Each face uses three of the four nodes. Face 1: nodes 1, 2, 3. Face 2: nodes 1, 4, 2. Face 3: nodes 2, 4, 3. Face 4: nodes 3, 4, 1.
The tet is the simplest solid element and the easiest to mesh complex geometry with. It is also the least accurate for bending problems, which is why the quadratic version — the C3D10 — is far more common in production models.
The Quadratic Tetrahedral Family — The Ten-Noded Tet
The C3D10 and C3D10M are ten-noded quadratic tetrahedra. The same four corner nodes as the linear tet, plus six midside nodes — one on each edge. Each face is now a six-noded quadratic triangle: three corners and three midsides.
This is where visualization gets more complex. The face itself is not flat — the midside nodes can be offset from the straight edge between corners, allowing the element to represent curved geometry. Drawing that curved face directly in a viewer is expensive. The solution is subdivision, which we will cover shortly.
The Wedge — Six and Fifteen Node
The C3D6 is a six-noded triangular prism — a wedge shape. It has two triangular faces and three rectangular faces. The C3D15 adds midside nodes to every edge.
Wedge elements appear frequently at the transition zones between hexahedral and tetrahedral meshes, where the mesher needs to bridge between the two topologies. They are less common than hexes and tets but the face extractor has to handle them.
Shell and Surface Elements
Shell elements like S4R and S3R are fundamentally different. They are two-dimensional — they represent a surface with an associated thickness, not a volume. Their face for visualization purposes is the element itself: a quadrilateral or triangle lying in space.
For the exterior surface algorithm, shells are handled differently from solids. A shell element's visible surface is the element face directly — there is no interior to subtract away. The program recognizes the shell type and takes the element faces as-is.
Membrane elements like M3D4R work the same way — they are purely surface elements with negligible structural thickness, used for things like thin film or flexible circuit overlays.
Non-Geometric Elements and the Filter
Before any face extraction runs, a pre-filter removes non-geometric elements. The program maintains a set of element types that have no spatial extent: MASS elements, SPRING1, SPRING2, SPRINGA, DASHPOT1, DASHPOT2, connector elements, rotary inertia elements.
These have no faces, no nodes in the geometric sense, and no contribution to the visible surface. If they are not filtered out before face extraction, the code will attempt to look up face topology for element types that have no topology table entry, producing errors or garbage output.
The filter is simple: check the element type string against the non-geometric set. Normalize the string first — strip whitespace and convert to uppercase — because type strings from different file sources can have inconsistent formatting.
Section 3 — The Face Key: Making Faces Comparable
The face counting algorithm needs to compare faces across different elements. Two elements share a face if and only if they both define a face with exactly the same set of node IDs.
But there is a subtle problem. Element A defines a face with nodes 100, 200, 300, 400 listed in that order. Element B, which shares that face, defines it with nodes 200, 300, 400, 100 listed in that order — rotated. Or even 400, 300, 200, 100 — reversed, because it is approaching the shared face from the other side.
If you compare the face lists as ordered sequences, these look like different faces. They are actually the same face described from different perspectives.
The solution is the face key. A face key is a canonical representation of a face that is independent of the order in which its nodes are listed. The most common approach is to sort the node ID list before comparison.
So nodes 200, 300, 400, 100 become the sorted tuple 100, 200, 300, 400. Nodes 400, 300, 200, 100 also become 100, 200, 300, 400. Both hash to the same key in a dictionary, and the counting works correctly regardless of how each element happens to list its face nodes.
In Python, this sorted tuple is kept as a hashable tuple, usable as a dictionary key. The face counting dictionary has face keys as its keys and integer counts as its values. After one pass through all elements of all types, the dictionary tells you exactly how many elements claim each face.
One more subtlety: winding order. When we sort the node IDs for counting purposes, we lose the original ordering. But the original ordering encodes the face normal direction — which way the face points outward. We need that for rendering.
So the implementation preserves the original face node list alongside the sorted key. The key is used for counting and comparison. The original ordered list is used for triangle generation and normal calculation. The V14.4 improvement to the code — credited in the version history to the Grok advisory review — specifically addressed this: face normalization was improved to preserve winding order even as the canonical key discards it.
Section 4 — Winding Order and Surface Normals
A face normal is a vector perpendicular to the face, pointing outward away from the part. You need normals for two things: lighting calculations in the 3D viewer so the part looks solid instead of flat, and consistency checking to make sure all faces point the right direction.
The normal direction is determined by the winding order — the sequence in which the face's corner nodes are listed. The convention in most computational geometry systems, and in Abaqus, is the right-hand rule: if you curl the fingers of your right hand in the direction the nodes are listed, your thumb points in the direction of the outward normal.
For a four-noded rectangular face listed as nodes A, B, C, D in counterclockwise order when viewed from outside the element, the normal points outward. If you accidentally reverse the order to A, D, C, B, the normal flips inward.
The face topology table for each element type is defined with a specific winding convention. For the C3D8 brick, each of the six faces is listed with nodes ordered counterclockwise when viewed from outside the element. This is why the face definition matters — it is not just which nodes, it is which order.
To compute the actual normal vector, you take two edge vectors of the face — say, from node A to node B, and from node A to node D — and compute their cross product. The cross product of two vectors in a plane gives a vector perpendicular to that plane. The direction follows the right-hand rule from the winding order.
For a perfect element, this calculation is exact. For a distorted element — one where the nodes have been pushed around by meshing to fit curved geometry — the face is no longer perfectly flat. Different triangulation paths across the same quadrilateral face give slightly different normals.
This is one reason why the geometry engine triangulates quadrilateral faces: triangles are always planar by definition, since three points define a unique plane. A four-node quad face may not be planar. Breaking it into two triangles guarantees planarity for each piece and consistent normal calculation.
The split is typically along one diagonal: nodes A, B, C form the first triangle, and nodes A, C, D form the second. The choice of diagonal can affect visual quality for highly distorted quads, but for most engineering meshes the difference is negligible.
Section 5 — Quadratic Element Subdivision
Linear elements have straight edges. Their faces are flat polygons. Rendering a mesh made of linear elements with the face counting algorithm produces an accurate but faceted surface — you can see every element face as a distinct flat patch.
Quadratic elements are different. A C3D10 tetrahedron or a C3D20 brick has midside nodes — extra nodes positioned along the edges between corners. These midside nodes can lie off the straight line between their corner neighbors, allowing the element edges to curve. And if the edges curve, the faces curve.
A direct rendering of a quadratic element's face using only its corner nodes produces a surface that visually misrepresents the element's actual geometry. You lose the curvature. A cylindrical feature meshed with quadratic elements would look like a polygon rather than a cylinder.
Subdivision Approach
The program solves this with face subdivision. When processing a quadratic element face, instead of producing one or two large triangles from the corner and midside nodes, the code subdivides the face into a grid of smaller triangles. The corners of each small triangle are computed using the element's shape functions — the mathematical interpolation rules that define where any point inside the element maps to in physical space.
The subdivision level controls how fine this grid is. A subdivision level of 1 produces 4 sub-triangles per face. Level 2 produces 16. Level 3 produces 64. Level 4 produces 256. The default in the program is level 2 — 16 sub-triangles per quadratic face — which gives smooth, accurate curved surface rendering for most models without excessive triangle count.
Shape function interpolation works as follows. Each node in the quadratic element has an associated shape function — a polynomial expression in the element's natural coordinates, which are a local coordinate system that maps the physical element to a standard reference shape. The value of the shape function at any point gives the weight of that node's contribution to the position of that point.
To find the physical coordinates of a subdivision grid point, you sum up each node's coordinates multiplied by the node's shape function value evaluated at the grid point's natural coordinates. The result is a position on the actual curved surface — not on the straight-edge approximation.
This is the same mathematics that Abaqus itself uses internally when it computes stresses at Gauss points and extrapolates them to nodes. The shape functions are not approximations — they are the defining representation of the element's geometry. Using them for visualization produces a surface that is faithful to how Abaqus actually understands the element's shape.
The toggle between quadratic and linear visualization is exposed in the program's settings. If you are working with a large model and the quadratic subdivision is slowing things down, you can switch to linear rendering for faster preview. If you need the most accurate surface for STL export, quadratic is the right choice.
Section 6 — The extract_surface_triangles Function
All of the logic we have discussed — face topology, face keys, winding order, quadratic subdivision — comes together in the function extract_surface_triangles. This is the workhorse of the geometry engine.
The function takes four inputs: a set of element IDs defining which elements belong to the part being processed, the full elements list, the node coordinate dictionary, and two flags — a boolean for whether to use quadratic subdivision, and an integer for the subdivision level.
It returns a list of triangles. Each triangle is three sets of three-dimensional coordinates — nine floating-point numbers representing the three corners of one triangle in physical space.
Step One — Filter to Part Elements
The first thing the function does is filter the element list to only those elements belonging to the current part. This sounds obvious, but the implementation requires care.
In a structured model — one with explicit part blocks — elements from different parts can have overlapping ID numbers. If the filter works by element ID alone, it will inadvertently include elements from other parts with the same IDs.
The correct filter is part-name-first. The program maintains a part-aware element retrieval method that first filters by the part name tag stored on each element during parsing, then within those results uses the element IDs as a secondary filter. This is the V15.6 fix documented in the version history.
Before this fix, certain multi-part structured models were producing surfaces with faces from neighboring parts included — because the ID-only filter was grabbing elements from the wrong part. The visual result was a surface that appeared correct at a glance but contained faces from the wrong geometry. That kind of silent error is exactly what this series is designed to help you recognize and reason about.
Step Two — Connectivity Validation
Before face extraction begins, every element passes through a connectivity validation check. The program maintains a minimum node count table: C3D4 requires 4 nodes, C3D8 requires 8, C3D10 requires 10, C3D20 requires 20, and so on.
Any element whose node list is shorter than the minimum for its type is flagged and excluded. These are malformed elements — most commonly produced when a file has multi-line element definitions where the continuation lines were not properly joined during parsing.
This validation step was added in V15.7 after a specific bug: large models with C3D10M and C3D20R elements were throwing a list index out of range error during STL export. The error occurred inside the face topology lookup when the code tried to access node position 9 in a list that only contained 8 entries. The element had been parsed with one continuation line missing.
The fix was not to change the face extraction logic. The fix was to catch invalid elements before they reached the extractor. Defensive programming: never let bad data propagate into downstream computation.
Step Three — The Face Count Pass
With a clean list of valid part elements, the function builds a face count dictionary. For each element, it looks up the face topology for that element type, extracts each face as a sorted tuple of node IDs, and increments the counter for that face key.
Alongside the count, it also stores the original ordered face node list — the one that preserves winding — associated with each face key. If a face is seen only once, this stored list is used for triangle generation. If a face is seen twice, it is discarded.
Step Four — Triangle Generation
For each exterior face — those with a count of exactly one — the function generates the triangles.
For linear faces: quadrilateral faces split into two triangles along the diagonal. Triangular faces are already triangles. Each triangle's three corners are looked up in the node coordinate dictionary to retrieve actual spatial positions.
For quadratic faces, when quadratic subdivision is enabled: the face node list is passed to the shape function interpolation routine, which generates a grid of physical positions at the requested subdivision level and returns the sub-triangles.
The result is a list of triangles — coordinate triplets — representing the exterior surface of the part. This list is what flows into the 3D viewer and the STL exporter.
Section 7 — The 3D Viewer: Rendering the Assembly
The 3D viewer in this program is built on matplotlib — the same plotting library used for the material property graphs in the Materials tab. The specific components used are the three-dimensional axes class, which provides a genuine three-dimensional coordinate space you can orbit, and the Poly3DCollection class, which renders a list of polygons — in our case, triangles — as a solid surface with shading.
Matplotlib is not a dedicated 3D rendering engine. Tools like OpenGL or VTK would produce faster, higher-quality visualization. But matplotlib is bundled with most Python scientific distributions, requires no separate installation, and provides perfectly functional visualization for engineering mesh review at the scale this tool targets. The explicit design choice is: zero additional dependencies for visualization.
From Triangles to Poly3DCollection
The triangle list output by extract_surface_triangles is a Python list of triplets, where each triplet is three three-dimensional points. Matplotlib's Poly3DCollection accepts exactly this format.
The collection is created with a face color, an edge color, and an alpha transparency value. The default alpha for multi-part viewing is 0.7 — seventy percent opaque. This allows you to see through the front parts to the parts behind them, which is useful for assembly review.
Edge rendering is enabled by default with a thin dark edge color. This draws the triangle edges on the surface, giving you a wireframe-over-shaded appearance that makes it easy to see the mesh density without losing the surface shading.
Lighting and Shading
Matplotlib's three-dimensional shading computes a simple ambient plus directional lighting model. It uses the face normals — derived from the winding order described earlier — to determine how much of the simulated light source each face receives. Faces pointing toward the light source appear brighter. Faces pointing away appear darker.
This is not physically accurate rendering. It is the same Gouraud-style shading that was common in early 3D graphics. For engineering purposes — distinguishing faces, identifying concavities, orienting parts — it is entirely sufficient.
If the face normals are inverted — pointing inward rather than outward — the shading inverts: faces pointing toward the viewer appear dark, concavities appear bright, and the part looks like a photographic negative of itself. This is the most visible symptom of a winding order error, and it is immediately obvious in the viewer.
Part Colors and the Default Palette
When viewing a multi-part assembly, each part is rendered in a distinct color. The program maintains a default palette of ten RGB color tuples: blue, orange, green, pink, yellow, purple, cyan, coral, light green, and mauve. Parts cycle through this palette in order.
You can override any part's color using the color picker dialog, which stores selections as RGB tuples in the program's preferences file. Custom colors persist across sessions for up to fifty parts — a cap that prevents the preferences file from growing unbounded in large assembly work.
Axes, Orientation, and Orbit Control
The three-dimensional axes in matplotlib support orbit interaction — click and drag to rotate the view, scroll to zoom, right-click drag to pan. The axes are automatically scaled to the bounding box of the triangle set so the part fills the view.
The initial view angle is set with an elevation and azimuth — twenty degrees above horizontal, rotated forty-five degrees. This isometric-like starting view shows three faces of a rectangular part simultaneously, which is generally the most useful orientation for first-look review.
The aspect ratio is set to equal in all three dimensions, so a cube-like part looks like a cube, not a rectangle. This sounds trivial but matplotlib's default behavior is to scale each axis independently based on data range, which distorts the visual shape of the part.
Section 8 — Auto-Decimation: Managing Triangle Count
Every quadratic element in a model produces multiple subdivision triangles per face. A C3D10M tet at subdivision level 2 produces four faces, each with 16 sub-triangles, for 64 triangles per element. A model with fifty thousand C3D10M elements produces over three million triangles.
Matplotlib becomes sluggish above roughly one hundred thousand triangles. The Poly3DCollection render time scales roughly linearly with triangle count, and at three million triangles the viewer would be non-interactive.
The program sets a default triangle threshold of one hundred thousand. When the extracted triangle count exceeds this threshold and auto-decimation is enabled, the program triggers a simplification step before rendering.
What Decimation Does
Triangle decimation — also called mesh simplification — reduces the number of triangles in a surface while attempting to preserve its overall shape. The simplest approach is random removal: discard triangles randomly until you reach the target count. This is fast but produces visual artifacts — holes, missing features, irregular gaps.
More sophisticated approaches preserve edge features and curvature by prioritizing which triangles to remove. The Quadric Error Metrics algorithm, developed in the late nineteen-nineties, is the standard for high-quality mesh simplification. It collapses edges selectively, choosing each collapse to minimize the change in surface shape as measured by a quadric error function.
The program offers a quality slider in the multi-part settings dialog: Full mesh at 100%, High Quality at 75%, Balanced at 50%, Fast Preview at 25%, and Draft at 10%. These correspond to the fraction of triangles retained after decimation.
Curvature-Based Quality Recommendations
Introduced in V15.5, the per-part STL export dialog analyzes the curvature of each part's extracted triangles before presenting the quality settings. It computes a curvature score from the distribution of face normal angles across neighboring triangles — parts with smooth planar surfaces have low curvature scores, and parts with curved features or sharp edges have higher scores.
Based on the curvature score, the dialog pre-selects a recommended quality level for each part. A flat plate might be recommended at 25% — decimation does not hurt it. A spherical housing with complex curved features gets a recommendation of 75% or higher — decimation would lose important shape information.
The use-recommended-defaults button applies all recommendations at once, which is especially useful when exporting assemblies with many parts. You can review and override individual part settings on the paged dialog, ten parts per page.
There is an important conceptual distinction here. Decimation for visualization is acceptable. Decimation for measurement or analysis is not. If you are exporting an STL to visually check assembly clearances in a downstream CAD tool, a 25% triangle count is fine. If you are exporting an STL to run a secondary FEA or to measure surface areas, you want the full mesh.
The program does not enforce this distinction — it presents the tools and lets you decide. Understanding why the choice matters is what this series is for.
Section 9 — The STL File Format: Under the Hood
STL is one of the simplest and most durable file formats in engineering. It stands for stereolithography — named after the early additive manufacturing process for which it was developed. Today it is used everywhere: 3D printing, CAD import, FEA preprocessing, visualization.
An STL file contains a flat list of triangles. That is it. No hierarchy, no material, no part names, no colors, no units. Just triangles. Each triangle has three vertex coordinates and an optional surface normal.
STL exists in two forms: ASCII and binary.
ASCII STL
The ASCII format is human-readable. The file opens with the keyword 'solid' followed by an optional name. Then each triangle is listed with its normal vector and three vertex coordinate lines. The file closes with 'endsolid' followed by the name.
ASCII STL is useful for debugging and for small models because you can open it in a text editor and read it directly. But it is bulky: each floating-point number is written as text, taking anywhere from 8 to 20 characters. A million-triangle model in ASCII might be several hundred megabytes.
Binary STL
The binary format is compact. The file starts with an 80-byte header — historically a comment field, often used today to store metadata. Then a 4-byte unsigned integer giving the total triangle count. Then the triangles: each stored as 12 four-byte floating-point numbers (one normal vector and three vertex positions) plus a 2-byte attribute byte count field that is almost always zero.
Each triangle in binary STL costs exactly 50 bytes. A million-triangle model in binary is 50 megabytes — roughly six to ten times smaller than the same model in ASCII. Binary is the default in the program for all exports above a small size threshold.
What the Program Writes
The STL exporter takes the triangle list — the same list produced by extract_surface_triangles — and writes it to disk. For each triangle, it computes the face normal from the cross product of the two edge vectors, then writes the normal and three vertices.
The normal in the file is technically redundant — any conforming STL reader is supposed to compute normals from the vertex ordering rather than trust the stored value. But including correct normals is good practice because some tools use the stored values as a shortcut, and incorrect normals produce rendering artifacts.
Vertex Welding
One optional processing step is vertex welding: merging vertices from different triangles that are at the same location in space into a single shared vertex. An un-welded mesh has separate vertex records for every corner of every triangle — which means two adjacent triangles that share an edge have two separate copies of each shared vertex.
Welding is important for smooth shading calculations in downstream tools, because shading algorithms average normals across shared vertices to produce smooth gradients. Without welding, every vertex is unique and every triangle shades independently — producing the faceted look.
The program exposes a weld tolerance parameter: the maximum distance between two vertices for them to be merged. A value of zero means exact coincidence only. A small positive value handles floating-point round-off. The default is zero, which is conservative — correct but may leave some jagged edges at boundaries between element faces in the output file.
Section 10 — Part-Aware Node Lookup: A Critical Subtlety
The geometry engine has a centralized method for retrieving node coordinates for a given part. This method — get_part_node_coords — is one of the places where architectural decisions made early in the program pay dividends.
The challenge is that node coordinates may be stored in different formats depending on the model type. For orphan meshes, node IDs are integers and the coordinate dictionary uses integer keys. For structured models, node IDs within parts can overlap, and the dictionary uses tuple keys: part-name, node-ID.
The node lookup method has to be smart about this. When asked for the coordinates of node 500 in the part named 'housing', it cannot just look up the key 500 — that might return coordinates belonging to a node 500 in a completely different part.
The method follows a resolution strategy with multiple fallback levels.
First, it tries the tuple key with the exact part name and the node ID. This is the canonical case for structured models.
If that fails — possibly because the part name as stored in the element record uses a slightly different format than the part name as stored in the node dictionary — it tries the base part name with underscores and suffixes stripped.
If that fails, it scans all tuple keys for any entry whose node ID component matches, regardless of part name. This broader search handles cases where the assembly export gave the node a slightly different part attribution than the element.
If that fails, it tries the integer key directly, for models where the node might have been recorded outside any part block.
This multi-strategy resolution is the product of debugging real models — not theoretical edge cases. Actual assembly exports from different Abaqus workflows produce subtly different node key formats. A lookup that only handles the happy path will fail silently on a significant fraction of real files.
The result of the method is a flat integer-keyed coordinate dictionary containing only the nodes belonging to the requested part. This dictionary is what gets passed to the face extractor and the STL writer. Every downstream function works with clean, unambiguous data.
Consider what this represents in terms of software design philosophy. The node lookup method is an abstraction boundary. Everything above it — the viewer, the STL exporter, the volume calculator — does not need to know whether the underlying storage uses integer keys or tuple keys or something else. It asks for a part's nodes, it gets a clean dictionary, and it proceeds.
Abstraction boundaries like this are what make a codebase maintainable. If the storage format ever changes — say, to handle some new Abaqus export format with a third key structure — only the lookup method needs to be updated. The rest of the program works unchanged.
Section 11 — Shell Elements: A Special Case for Surface Identification
Shell elements deserve a dedicated section because their relationship with the exterior surface algorithm is fundamentally different from solid elements.
A solid element has volume. Its exterior faces are those faces that no neighboring element shares. The face counting algorithm finds them.
A shell element has no volume. It is a surface. It represents a thin physical component — a sheet metal panel, a circuit board, a flexible membrane — modeled as a two-dimensional surface in space with an associated thickness property. The element itself is the visible surface. There is no interior to exclude.
For a pure shell model, the face counting algorithm behaves differently. Apply the algorithm naively, and every shell element's face appears exactly once — because shells do not share faces with neighboring shells at the element level, they share edges. The count of one marks every face as exterior, which is correct.
But what about a mixed model? A circuit board assembly might have solid C3D8R elements for the solder balls and chip packages, S4R shell elements for the PCB substrate, and M3D4R membrane elements for thin film overlays. The face extractor has to handle all three element families in the same pass.
The program handles this by classifying elements before face extraction. The is_geometric_element_type function — a Gemini-review fix in the version history — accepts both solid elements in the geometric solid set and surface elements in the surface elements dictionary. The surface elements dictionary is populated by the STL exporter module and includes shell, membrane, and rigid surface types.
For surface elements, the visualization path uses the element face directly as a triangle or triangulated quadrilateral, applying the same winding order and normal calculation as for solid exterior faces. The quadratic subdivision logic applies here too: S8R shell elements have midside nodes and benefit from subdivision for curved shell geometries.
The membrane overlay technique is worth a specific mention here because it is a common point of confusion. A membrane element placed over a solid substrate uses the same material as the substrate. Its thickness is negligible — essentially zero structurally. Its purpose is to report the stress at the surface integration point of the substrate, which is where the surface of the physical component actually is.
When such a model is processed, the membrane's faces are on top of the substrate's outer faces. The face counting algorithm sees the substrate's outer face appearing once — exterior from the substrate's perspective. It also sees the membrane's face appearing once — exterior from the membrane's perspective. If these faces use the same node IDs, they may cancel each other out in the count and disappear.
This is a known edge case and one reason the program provides the dual-view option: the original file structure view may reveal the membrane as a separate entity even if the identified-parts view merges it with the substrate.
Section 12 — The Complete Geometry Pipeline, End to End
Here is the complete sequence consolidated into a single numbered list.
1. The user selects a part in the Parts tab and clicks View in 3D or Export STL.
2. The part-aware element retrieval method filters all elements to those belonging to the requested part, using the part name tag from parsing rather than element ID alone.
3. The connectivity validation filter checks every element's node list length against the minimum expected count for its type. Any element with an incomplete node list is excluded.
4. Non-geometric elements — MASS, SPRING, DASHPOT, and others — are filtered from the list.
5. The part-aware node lookup method builds a flat integer-keyed coordinate dictionary containing only nodes for this part, resolving tuple keys to integer keys through the multi-strategy fallback.
6. The face count pass runs over all elements. For each element, the face topology table returns the list of face definitions. Each face definition becomes a sorted tuple face key. The key increments a count in the face dictionary. The original ordered node list is stored alongside for winding preservation.
7. Face count equals one: exterior. Face count equals two: interior. Discard interior faces.
8. For each exterior face: look up node coordinates. If linear, split quads into two triangles. If quadratic and subdivision is enabled, generate the subdivision grid using shape function interpolation at the specified level.
9. The result is a list of triangles — coordinate triplets in physical space.
10. For 3D viewing: pass the triangle list to Poly3DCollection with the part's color, alpha, and edge settings. Render in the matplotlib three-dimensional axes. Apply equal-ratio axis scaling and an isometric starting orientation.
11. If the triangle count exceeds one hundred thousand and auto-decimation is enabled, the quality dialog appears. The curvature analysis pre-selects recommended quality levels per part. The user confirms or adjusts. Decimation runs. The reduced triangle list is rendered.
12. For STL export: the triangle list is written to disk in binary or ASCII format. Each triangle's face normal is computed from the cross product. The 80-byte header, triangle count, and per-triangle records are written in sequence. Vertex welding is applied if the weld tolerance is non-zero.
That is the complete geometry pipeline. From a parsed connectivity table to a three-dimensional rendering or a file on disk, with every step explicit and every decision traceable.
Section 13 — What Can Go Wrong, and What the Errors Tell You
A series about critical thinking has to include a section on failure modes. Understanding what can go wrong and why is how you develop diagnostic skill.
Inverted Surface — Wrong Winding Order
Symptom: In the 3D viewer, the part appears dark from the front and bright from the back. The lighting looks backwards.
Cause: The face winding order is inverted relative to the viewer's assumption. Either the face topology table has the nodes listed in the wrong order for a particular element type, or the model itself was exported with flipped element connectivity.
Diagnostic: Check one element manually. Look up its node IDs in the connectivity table, look up those nodes' coordinates, compute the face normal by hand using the cross product, and verify which direction it points relative to the element's center.
Missing Faces — Interior Faces Classified as Exterior
Symptom: The part surface has holes — you can see through it to the inside.
Cause: Some interior faces are appearing with a count of one instead of two. This happens when two adjacent elements that share a face define that face with different node orderings that, after sorting, produce different face keys.
For example: element A defines a face with nodes 100, 200, 300, 400. Element B defines what should be the same face with nodes 100, 300, 200, 400 — swapping two non-adjacent nodes. Sorted, A gives 100, 200, 300, 400. Sorted, B gives 100, 200, 300, 400. These match. But if B instead had 100, 200, 350, 400 — a slightly different node — the keys do not match, both faces appear once, and both are treated as exterior. You get a face on the inside of the model appearing as a visible surface.
In practice this is rare with well-formed meshes. It can appear in meshes that were manually edited, exported from non-standard tools, or stitched from multiple sources.
List Index Out of Range — Incomplete Element Connectivity
Symptom: An exception during STL export or 3D viewing. The error message says 'list index out of range.'
Cause: An element's node list is shorter than expected. The face topology table for the element type tried to access node at position 9, but the list only has 8 entries. This is the bug from the V15.7 fix discussed in Section 6.
Diagnostic: Look for multi-line element definitions in the input file for quadratic element types. Check whether continuation lines are properly joined in the parsed element records.
Resolution: The connectivity validation filter now catches this before the extractor runs. If you see this error in the console debug output and the extraction still succeeds, it means the filter caught the bad elements and excluded them, and the remaining valid elements produced the surface.
Empty Surface — No Triangles Extracted
Symptom: The viewer shows nothing. The STL export produces a zero-byte or near-zero-byte file.
Causes: The part was filtered to zero elements — either the part name lookup failed, all elements were non-geometric, or all elements failed connectivity validation. Or the face topology table has no entry for the element types in this part. Or all faces were shared — which should not happen for a physically valid closed mesh but can occur for a single-element test case.
Diagnostic: Check the debug console output. The program logs the number of elements retrieved for the part, the number of triangles extracted, and warnings about filtered or skipped elements. These messages trace the pipeline step by step and tell you exactly where the count went to zero.
Closing — Geometry as Information
The geometry engine is, at its foundation, an information extraction problem.
The mesh file contains everything needed to describe the exterior surface of every part. That information is implicit in the connectivity table — hidden in the counts of how many times each face appears. The geometry engine's job is to make that implicit information explicit: to surface the boundary from the interior, to convert connectivity data into renderable triangles, to write those triangles into a format that other tools can consume.
Every design decision in the pipeline has a reason.
The face key sorts nodes to handle order-independent matching. The winding order is preserved alongside the key to maintain normal direction. Quadratic subdivision uses shape functions — the same mathematics as the solver — to faithfully represent curved geometry. The connectivity validation filter stops bad data before it propagates. The part-aware element retrieval prevents cross-part ID collision. The multi-strategy node lookup handles format variations in real files.
None of these are arbitrary. Each one was added because a specific failure mode was observed on a real file. That is how production-grade software develops: not from theoretical completeness, but from the accumulated record of real cases that broke earlier, simpler versions.
In Part Three of this series, we will move into the material property system — how property datasets are stored, how the plotting engine selects axes and formats data, and how the recommendation engine uses material data to identify risks specific to the simulation type.
Everything described in this series is visible in the source code at McFaddenCAE.com.
End of Part 2 — The Geometry Engine
Next: Part 3 — Material Properties, Plotting, and the Recommendation Engine
© 2026 Joseph P. McFadden Sr. All rights reserved. | McFaddenCAE.com